#ifndef PLY_HPP
#define PLY_HPP
#include <fstream>
#include <vector>
#include <sstream>
//#include <opencv2\opencv.hpp>
#include "filebase.hpp"
#include <math.h>

class PLY
{

public:
	std::vector<float> xyz;
	std::vector<unsigned char> rgb;
	std::vector< std::vector<int> > face;
	std::vector<float> nxyz;

	int mode;


	PLY()
	{
		mode = 0;
	}
#if 0
	void FixNaN(PLY &dst)
	{
		dst.xyz.clear();
		dst.nxyz.clear();
		dst.rgb.clear();
		for (size_t i = 0; i < xyz.size(); i+=3)
		{
			if (isnan(nxyz[i]) || isnan(nxyz[i + 1]) || isnan(nxyz[i + 2]) || isnan(xyz[i]) || isnan(xyz[i + 1]) || isnan(xyz[i + 2]))
			{
				
			}
			else
			{
				dst.xyz.push_back(xyz[i]);
				dst.xyz.push_back(xyz[i+1]);
				dst.xyz.push_back(xyz[i+2]);
				dst.nxyz.push_back(nxyz[i]);
				dst.nxyz.push_back(nxyz[i + 1]);
				dst.nxyz.push_back(nxyz[i + 2]);
				dst.rgb.push_back(rgb[i]);
				dst.rgb.push_back(rgb[i + 1]);
				dst.rgb.push_back(rgb[i + 2]);
			}
		}
	}
#endif

	void Set(const std::vector<float> &xyz0, unsigned char r = 100, unsigned char g = 150, unsigned char b = 200)
	{
		size_t nr = xyz0.size() % 3;
		xyz.assign(xyz0.begin(), xyz0.end() - nr);
		rgb.assign(xyz.size(), 200);
		for (size_t i = 0; i < rgb.size(); i += 3)
		{
			rgb[i] = r;
			rgb[i + 1] = g;
			rgb[i + 2] = b;
		}
		nxyz.assign(xyz.size(), 0);
		face.clear();
	}

	void Set(const std::vector<float> &xyz0, const std::vector<float> &nxyz0, unsigned char r = 100, unsigned char g = 150, unsigned char b = 200)
	{
		size_t n1 = xyz0.size()-xyz0.size() % 3;
		size_t n2 = nxyz0.size() - nxyz0.size() % 3;
		if (n1 > n2)
			n1 = n2;
		xyz.assign(xyz0.begin(), xyz0.begin() +n1);
		nxyz.assign(nxyz0.begin(), nxyz0.begin() + n1);
		rgb.assign(xyz.size(), 200);
		for (size_t i = 0; i < rgb.size(); i += 3)
		{
			rgb[i] = r;
			rgb[i + 1] = g;
			rgb[i + 2] = b;
		}		
		face.clear();
	}

        void PadNormal()
        {
            nxyz.resize(xyz.size());
        }

        void PadColor()
        {
            rgb.resize(xyz.size());
        }

#if 0
	void Set(const cv::Mat &xyz0, const cv::Mat &color0, double near, double far)
	{
		for (size_t i = 0; i < xyz0.total(); i++)
		{
			float z = xyz0.at<cv::Vec3f>(i)[2];
			if (z > 0 && z > near && z < far)
			{
				xyz.push_back(xyz0.at<cv::Vec3f>(i)[0]);
				xyz.push_back(xyz0.at<cv::Vec3f>(i)[1]);
				xyz.push_back(z);

				rgb.push_back(color0.at<cv::Vec3b>(i)[2]);
				rgb.push_back(color0.at<cv::Vec3b>(i)[1]);
				rgb.push_back(color0.at<cv::Vec3b>(i)[0]);
			}
		}
		nxyz.assign(xyz.size(), 0);
	}
#endif
	template<typename T>
	static void Append(std::vector<T> &v1, const std::vector<T> &v2)
	{
		size_t n = v1.size();
		v1.resize(n + v2.size());
		std::copy(v2.begin(), v2.end(), v1.begin() + n);
	}

	void AppendVertex(const PLY &src)
	{
		PLY::Append<float>(xyz, src.xyz);
		PLY::Append<unsigned char>(rgb, src.rgb);
		PLY::Append<float>(nxyz, src.nxyz);
	}

	void OutputHeader(std::stringstream &ss)
	{
		ss << "ply\n";
		ss << "format ascii 1.0\n";
		ss << "comment UNRE generated\n";
		ss << "element vertex " << (xyz.size() / 3) << "\n";
		ss << "property float x\n";
		ss << "property float y\n";
		ss << "property float z\n";
		if (mode != 4)//for obj convertor
		{
			ss << "property float nx\n";
			ss << "property float ny\n";
			ss << "property float nz\n";
			ss << "property uchar red\n";
			ss << "property uchar green\n";
			ss << "property uchar blue\n";
		}
		ss << "element face " << face.size() << "\n";
		ss << "property list uchar int vertex_indices\n";
		ss << "end_header\n";
	}

	void Output(std::stringstream &ss)
	{
		OutputHeader(ss);

		for (size_t i = 0; i < xyz.size(); i += 3)
		{
			ss << xyz[i] << " " << xyz[i + 1] << " " << xyz[i + 2];
			if (mode != 4)//for obj convertor
			{
				ss << " ";
				ss << nxyz[i] << " " << nxyz[i + 1] << " " << nxyz[i + 2];
				ss << " ";
				ss << (unsigned int)rgb[i] << " " << (unsigned int)rgb[i + 1] << " " << (unsigned int)rgb[i + 2];
			}
			ss << "\n";
		}

		for (size_t i = 0; i < face.size(); i++)
		{
			ss << face[i].size();
			for (unsigned char j = 0; j < face[i].size(); j++)
				ss << " " << face[i][j];
			ss << "\n";
		}

	}

	void InputHeader(std::stringstream &ss, std::vector<std::string> &line)
	{
		std::string l = "";
		while (l.substr(0, 10).compare("end_header") != 0)
		{
			std::getline(ss, l);
			line.push_back(l);
		}
	}



	void InputFaceA(std::stringstream &ss)
	{
		for (size_t i = 0; i < face.size(); i++)
		{
			unsigned int vertexNum = 0;
			ss >> vertexNum;
			face[i].resize(vertexNum);
			for (unsigned char j = 0; j < vertexNum; j++)
				ss >> face[i][j];
		}
	}


	void InputFaceB(std::stringstream &ss)
	{
		for (size_t i = 0; i < face.size(); i++)
		{
			unsigned char vertexNum = 0;
			ss.read((char*)(&vertexNum), sizeof(vertexNum));
			face[i].resize(vertexNum);
			for (unsigned char j = 0; j < vertexNum; j++)
			{
				int v;
				ss.read((char*)(&v), sizeof(v));
				face[i][j] = v;
			}
		}
	}

	void InputFaceB2(std::stringstream &ss)
	{
		for (size_t i = 0; i < face.size(); i++)
		{
			unsigned char vertexNum = 0;
			ss.read((char*)(&vertexNum), sizeof(vertexNum));
			face[i].resize(vertexNum);
			for (unsigned char j = 0; j < vertexNum; j++)
			{
				int v;
				ss.read((char*)(&v), sizeof(v));
				face[i][j] = v;
			}
			int v;
			ss.read((char*)(&v), sizeof(v));
		}
	}

	void InputVertexA(std::stringstream &ss)
	{
		for (size_t i = 0; i < xyz.size(); i += 3)
		{
			for (size_t j = 0; j < 3; j++)
			{
				ss >> xyz[i + j];
			}
			for (size_t j = 0; j < 3; j++)
			{
				ss >> nxyz[i + j];
			}
			for (size_t j = 0; j < 3; j++)
			{
				unsigned int charv;
				ss >> charv;
				rgb[i + j] = charv;
			}
		}
	}

	void InputVertexA2(std::stringstream &ss)
	{
		for (size_t i = 0; i < xyz.size(); i += 3)
		{
			for (size_t j = 0; j < 3; j++)
			{
				ss >> xyz[i + j];
			}
			for (size_t j = 0; j < 3; j++)
			{
				unsigned int charv;
				ss >> charv;
				rgb[i + j] = charv;
			}
		}
	}

	void InputVertexA3(std::stringstream &ss)
	{
		for (size_t i = 0; i < xyz.size(); i += 3)
		{
			for (size_t j = 0; j < 3; j++)
			{
				ss >> xyz[i + j];
			}
		}
		rgb.assign(xyz.size(), 255);
		nxyz.assign(xyz.size(), 0);
	}

	void InputVertexB(std::stringstream &ss)
	{
		for (size_t i = 0; i < xyz.size(); i += 3)
		{
			for (size_t j = 0; j < 3; j++)
			{
				float v;
				ss.read((char*)(&v), sizeof(v));
				xyz[i + j] = v;
			}
			for (size_t j = 0; j < 3; j++)
			{
				float v;
				ss.read((char*)(&v), sizeof(v));
				nxyz[i + j] = v;
			}
			for (size_t j = 0; j < 3; j++)
			{
				unsigned char v;
				ss.read((char*)(&v), sizeof(v));
				rgb[i + j] = v;
			}
		}
	}

	void InputVertexB2(std::stringstream &ss)
	{
		for (size_t i = 0; i < xyz.size(); i += 3)
		{
			for (size_t j = 0; j < 3; j++)
			{
				float v;
				ss.read((char*)(&v), sizeof(v));
				xyz[i + j] = v;
			}
			for (size_t j = 0; j < 3; j++)
			{
				unsigned char v;
				ss.read((char*)(&v), sizeof(v));
				rgb[i + j] = v;
			}
		}
	}

	void InputVertexB3(std::stringstream &ss)
	{
		for (size_t i = 0; i < xyz.size(); i += 3)
		{
			for (size_t j = 0; j < 3; j++)
			{
				float v;
				ss.read((char*)(&v), sizeof(v));
				xyz[i + j] = v;
			}

			float v;
			ss.read((char*)(&v), sizeof(v));
			for (size_t j = 0; j < 3; j++)
			{
				nxyz[i + j] = v;
			}
		}
		rgb.assign(xyz.size(), 255);
	}



	void InputVertexB4(std::stringstream &ss)
	{
		for (size_t i = 0; i < xyz.size(); i += 3)
		{
			for (size_t j = 0; j < 3; j++)
			{
				float v;
				ss.read((char*)(&v), sizeof(v));
				xyz[i + j] = v;
			}
			for (size_t j = 0; j < 3; j++)
			{
				float v;
				ss.read((char*)(&v), sizeof(v));
				nxyz[i + j] = v;
			}
			int v;
			ss.read((char*)(&v), sizeof(v));
		}
		rgb.assign(xyz.size(), 255);
	}

	void InputVertexB5(std::stringstream &ss)
	{
		for (size_t i = 0; i < xyz.size(); i += 3)
		{
			for (size_t j = 0; j < 3; j++)
			{
				float v;
				ss.read((char*)(&v), sizeof(v));
				xyz[i + j] = v;
			}
			
			{
				float v;
				ss.read((char*)(&v), sizeof(v));
				for (size_t j = 0; j < 3; j++)
					nxyz[i + j] = v;
			}
			for (size_t j = 0; j < 3; j++)
			{
				unsigned char v;
				ss.read((char*)(&v), sizeof(v));
				rgb[i + j] = v;
			}
		}
	}




	void Input(std::stringstream &ss)
	{
		std::vector<std::string> line;
		InputHeader(ss, line);
		std::string format;
		for (auto &l : line)
		{
			if (l.substr(0, 14).compare("element vertex") == 0)
			{
				size_t vertexNum = 0;
				std::istringstream(l.substr(15)) >> vertexNum;
				xyz.resize(vertexNum * 3);
				rgb.resize(vertexNum * 3);
				nxyz.resize(vertexNum * 3);
			}

			if (l.substr(0, 12).compare("element face") == 0)
			{
				size_t faceNum = 0;
				std::istringstream(l.substr(13)) >> faceNum;
				face.resize(faceNum);
			}

			if (l.substr(0, 6).compare("format") == 0)
			{
				format = l.substr(7);
			}
		}


		if (format.compare("ascii 1.0") == 0)
		{
			switch (mode)
			{
			case 1:
				InputVertexA2(ss);
				break;
			case 4://for surfacetrim lib
				InputVertexA3(ss);
				break;
			default:
				InputVertexA(ss);
				break;
			}
			InputFaceA(ss);
		}
		else
		{
			switch (mode)
			{
			case 1:
				InputVertexB2(ss);
				break;
			case 2://for surface reconstruct and trimmer
				InputVertexB3(ss);
				break;
			case 3://for meshlabserver.exe
				InputVertexB4(ss);
				break;			
			case 5://for new surfacetrimmer
				InputVertexB5(ss);
				break;
			default:
				InputVertexB(ss);
				break;
			}
			switch (mode)
			{
			case 3://for meshlabserver.exe
				InputFaceB2(ss);
				break;
			default:
				InputFaceB(ss);
				break;
			}
		}
	}


	bool Save(std::string path)
	{
		std::stringstream ss;
		Output(ss);
		return FileBase::SaveBinaryStream(path.c_str(), ss);
	}

	bool Load(std::string path)
	{
		std::stringstream ss;
		if (FileBase::LoadBinaryStream(path.c_str(), ss))
		{
			Input(ss);
			return true;
		}
		return false;
	}

};


#endif
